using System;
using System.Collections.Generic;
using System.Text;
using Zsa.Emulation.Memory;
using System.IO;
using Zsa.Emulation.Cartridge;
using System.Drawing;
using System.Drawing.Imaging;
using Zsa.Emulation.Util;
using Zsa.Emulation.Mappers;

namespace Zsa.Emulation.PPU
{
    /// <summary>
    /// The pattern table.
    /// </summary>
    public class PatternTable : MapperMemoryBank
    {


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:PatternTable"/> class.
        /// </summary>
        /// <param name="tableStream">The table stream.</param>
        /// <param name="tileOffset">The tile offset.</param>
        /// <param name="streamOffset">The stream offset.</param>
        internal PatternTable(Mapper mapper, MemoryStream stream, int offset, bool isBankTwo )
            : base(mapper, stream, RomBankType.VideoRom | (isBankTwo?RomBankType.BankTwoFlag : RomBankType.Unknown), offset)
        {
        }
        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the <see cref="T:PatternTile"/> at the specified index.
        /// </summary>
        /// <value></value>
        public PatternTile this [ int index ]
        {
            get
            {
                // The index of the tile in the byte array.
                int _Index = index * 16;
                // The bytes.
                byte[] _Bytes = new byte[16];
                // Copy the tile.
                Buffer.BlockCopy(_Buffer, _Index + InternalOffset, _Bytes, 0, 16);
                // The tile.
                PatternTile _Tile;
                unsafe
                {
                    // Get a pointer to the tile.
                    PatternTile* _TilePtr = &_Tile;
                    // Fix the buffer.
                    fixed (byte* _BufferPtr = _Bytes)
                    {
                        // Get pointer to fixed byte.
                        ulong* _BufferTemp = (ulong*)(_BufferPtr);// + _Index);
                        // Get the pointer to the temp tile.
                        ulong* _TileUlngPtr = (ulong*)_TilePtr;
                        // Set the first 8 bytes.
                        *_TileUlngPtr = *_BufferTemp;
                        // Move both pointers addresses up by 8.
                        _TileUlngPtr++;
                        _BufferTemp++;
                        // Set the next 8 bytes.
                        *_TileUlngPtr = *_BufferTemp;
                    }
                }
                return _Tile;
            }
            set{
                throw new NotImplementedException();
            }
        }
        #endregion Properties

        #region Methods

        /// <summary>
        /// Reads the tiles.
        /// </summary>
        /// <param name="tileStream">The tile stream.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="count">The count.</param>
        /// <returns></returns>
        public static PatternTile[] ReadAllTiles(Stream tileStream,
            int offset, int length)
        {
            // Make sure the stream is not null.
            if (tileStream == null)
            {
                throw new ArgumentNullException("tileStream");
            }
            // Make sure the count is divisible by the patterntilesize so we dont have extra bytes
            if ((length % PatternTile.PatternTileByteSize) != 0)
            {
                throw new ArgumentOutOfRangeException("count");
            }
            // Get the number of tiles.  
            int _TileCount = length / PatternTile.PatternTileByteSize;
            // Create teh table.
            PatternTile[] _Tiles = new PatternTile[_TileCount];
            // Go to the offset.
            tileStream.Seek(offset, SeekOrigin.Begin);
            //The tile index.
            int _TileIndex = 0;
            // The bytes for each tile.
            byte[] _BufferTiles = new byte[PatternTile.PatternTileByteSize];
            System.Diagnostics.Stopwatch a = System.Diagnostics.Stopwatch.StartNew();
            unsafe
            {
                // Temp tile.
                PatternTile _Tile = new PatternTile();
                // get the address of the temp tile.
                PatternTile* _TilePtr = &_Tile;
                // Loop through the tiles.
                while (_TileIndex < _TileCount)
                {
                    // Read the structure from the stream
                    tileStream.Read(_BufferTiles, 0, PatternTile.PatternTileByteSize);
                    fixed (byte* _BufferPtr = _BufferTiles)
                    {
                        // Get pointer to fixed byte.
                        ulong* _BufferTemp = (ulong*)_BufferPtr;
                        // Get the pointer to the temp tile.
                        ulong* _TileUlngPtr = (ulong*)_TilePtr;
                        // Set the first 8 bytes.
                        *_TileUlngPtr = *_BufferTemp;
                        // Move both pointers addresses up by 8.
                        _TileUlngPtr++; 
                        _BufferTemp++;
                        // Set the next 8 bytes.
                        *_TileUlngPtr = *_BufferTemp;
                    }
                    _Tiles[_TileIndex] = _Tile;
                    _TileIndex++;
                }

            }
            a.Stop();
            TraceLogger.TraceInformation(
            "Read tiles {0}.  Time {1}",
                _TileCount,
                a.Elapsed.ToString());
            return _Tiles;
        }

        ///// <summary>
        ///// Creates an 8bit image.
        ///// </summary>
        ///// <returns></returns>
        //public Bitmap CreateImage()
        //{
        //    return CreateImage(PaletteSettings.CreateDefault());
        //}

        /// <summary>
        /// Creates the image.
        /// </summary>
        /// <returns></returns>
        public Bitmap CreateImage()
        {
            return CreateImage(
                ReadAllTiles(new MemoryStream(_Buffer), _InternalOffset, VideoMemoryConstants.PatternTableByteSize),
                PaletteTable.CreateMonochrome(ColorPaletteType.Background),
                16
            );
        }

        /// <summary>
        /// Creates an 8bit image.
        /// </summary>
        /// <param name="tiles">The tiles.</param>
        /// <param name="palette">The palette.</param>
        /// <param name="tileDrawWidth">The number of tiles to draw across.</param>
        /// <returns></returns>
        private Bitmap CreateImage(PatternTile[] tiles,
            PaletteTable palette, int tileDrawWidth)
        {
            System.Diagnostics.Stopwatch a = System.Diagnostics.Stopwatch.StartNew();
            // Check to make sure we have tiles.
            if (tiles.Length < 1)
            {
                throw new IndexOutOfRangeException("The are no tiles to draw !");
            }
            // The width and height by tile.
            int _TileHeight = tiles.Length / tileDrawWidth;
            // The Y offset.
            int _YOffset = 0, _XOffset = 0;
            // Create a rectangle for the image area.
            Rectangle _ImageRectangle =
                new Rectangle(
                    0, 0,
                    tileDrawWidth * PatternTile.TileWidth,
                    _TileHeight * PatternTile.TileHeight
                );
            // Create the bitmap.
            Bitmap _Bitmap = new Bitmap(
                _ImageRectangle.Width,
                _ImageRectangle.Height,
                PixelFormat.Format32bppArgb
                );

            // Lock the bits.
            BitmapData _BitmapData =
                _Bitmap.LockBits(
                    _ImageRectangle,
                    ImageLockMode.ReadWrite,
                    PixelFormat.Format32bppArgb
                );
            // If an indexed format then fill the palette.
            if (_BitmapData.PixelFormat == PixelFormat.Format4bppIndexed ||
                _BitmapData.PixelFormat == PixelFormat.Format8bppIndexed ||
                _BitmapData.PixelFormat == PixelFormat.Format1bppIndexed)
            {
                //palette.FillColorPalette(_Bitmap.Palette);
            }
            // Index of the tile.
            int _TempIndex = 0;
            // The size of the pixel.
            byte _PixelSize = 4;
            unsafe
            {
                // The current row.  The length is _BitmapData.Scan0 * PatternTile.TileHeight; 
                // or PatternTile.TileHeight * PatternTile.TileWidth * _PixelSize
                byte* _Row = (byte*)_BitmapData.Scan0;
                // Loop throught the tile y.
                for (int _TileY = 0; _TileY < _TileHeight; _TileY++)
                {
                    // Set the y offset to the next row.
                    _YOffset = _TileY * PatternTile.TileHeight;
                    // Loop through the tiles accross.
                    for (int _TileX = 0; _TileX < tileDrawWidth; _TileX++)
                    {
                        // 
                        _XOffset =  ((_TileX * PatternTile.TileWidth) * _PixelSize);
                        // Set the pointer to the first pixel.
                        _Row = (byte*)_BitmapData.Scan0 +
                            ((_YOffset * _BitmapData.Stride) + _XOffset);
                        // Draw the tile.
                        tiles[_TempIndex].DrawTile(
                            _Row, 
                            _BitmapData.Stride,
                            _PixelSize, 
                            0, 
                            palette
                        );
                        _TempIndex++;
                    }
                }
            }
            _Bitmap.UnlockBits(_BitmapData);
            a.Stop();
            Console.WriteLine("Created bitmap.  Time : {0}",
                a.Elapsed.ToString());
            return _Bitmap;
        }
        public void CreateTestImages()
        {
            Image _Image = CreateImage(
                ReadAllTiles(new MemoryStream(_Buffer),_InternalOffset,VideoMemoryConstants.PatternTableByteSize),
                PaletteTable.CreateMonochrome(),
                16
            );
            
            _Image.Save("C:\\" +
                Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + ".PttnTbl.0.bmp",ImageFormat.Bmp);
        }
        #endregion Methods
    }
}
